package com.aliothservice.catfish.maintenance;

import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;

import com.aliothservice.catfish.order.data.CompleteVehicleOrder;
import com.aliothservice.catfish.order.data.ProductOrder;
import com.aliothservice.catfish.order.data.WarehouseOrder;
import com.aliothservice.catfish.product.data.ProductDataManager;
import com.aliothservice.catfish.product.data.ProductService;
import com.aliothservice.catfish.product.data.VehicleService;
import com.aliothservice.catfish.product.data.WarehouseService;
import com.mongodb.WriteResult;

public class MaintenanceController {

	public void dataCleanUp() {
		Date now = new Date();
		logger.info("data clean up is starting, it is " + now.toString() + " now");
		
		// check overdue product services
		Query overdueProductServiceQuery = new Query();
		Criteria overdueProductServiceDateCriteria = Criteria.where("availableUntil").lt(now);
		overdueProductServiceQuery.addCriteria(overdueProductServiceDateCriteria);
		List<ProductService> productServices = mongoTemplate.find(overdueProductServiceQuery, ProductService.class);
		for (ProductService productService : productServices) {
			logger.info("canceling product service by id: " + productService.getId());
			if (!productDataManager.cancelProductService(productService.getId())) {
				logger.info("failed to cancel product service, id: " + productService.getId());
			} else {
				logger.info("product service has been cancelled, id: " + productService.getId());
			}
		}
		
		// check overdue warehouse services
		Query overdueWarehouseServiceQuery = new Query();
		Criteria overdueWarehouseServiceDateCriteria = Criteria.where("untilDate").lt(now);
		overdueWarehouseServiceQuery.addCriteria(overdueWarehouseServiceDateCriteria);
		List<WarehouseService> warehouseServices = mongoTemplate.find(overdueWarehouseServiceQuery, WarehouseService.class);
		for (WarehouseService warehouseService : warehouseServices) {
			logger.info("canceling warehouse service by id: " + warehouseService.getId());
			if (!productDataManager.cancelWarehouseService(warehouseService.getId())) {
				logger.info("failed to cancel warehouse service, id: " + warehouseService.getId());
			} else {
				logger.info("warehouse service has been cancelled, id: " + warehouseService.getId());
			}
		}
		
		// check overdue vehicle services
		Query overdueVehicleServiceQuery = new Query();
		Criteria overdueVehicleServiceDateCriteria = Criteria.where("untilDate").lt(now);
		overdueVehicleServiceQuery.addCriteria(overdueVehicleServiceDateCriteria);
		List<VehicleService> vehicleServices = mongoTemplate.find(overdueVehicleServiceQuery, VehicleService.class);
		for (VehicleService vehicleService : vehicleServices) {
			logger.info("canceling vehicle servcie by id: " + vehicleService.getId());
			if (!productDataManager.cancelCompleteVehicleService(vehicleService.getId())) {
				logger.info("failed to cancel vehicle service, id: " + vehicleService.getId());
			} else {
				logger.info("vehicle service has been cancelled, id: " + vehicleService.getId());
			}
		}
		
		// remove overdue products
		Query overdueProductServiceRemovalQuery = new Query();
		Criteria overdueProductServiceRemovalStockCriteria = Criteria.where("stock").lte(Integer.valueOf(0));
		overdueProductServiceRemovalQuery.addCriteria(overdueProductServiceRemovalStockCriteria);
		List<ProductService> productServicesForRemoval = mongoTemplate.find(overdueProductServiceRemovalQuery, ProductService.class);
		for (ProductService productService : productServicesForRemoval) {
			logger.info("removing product service by id: " + productService.getId());
			WriteResult removalResult = mongoTemplate.remove(productService);
			if (1 != removalResult.getN()) {
				logger.info("failed to remove product service, id: " + productService.getId());
			} else {
				logger.info("product servcie has been removed, id: " + productService.getId());
			}
		}
		
		// remove overdue warehouses
		Query overdueWarehouseServiceRemovalQuery = new Query();
		Criteria overdueWarehouseServiceRemovalStockCriteria = Criteria.where("stock").lte(Integer.valueOf(0));
		overdueWarehouseServiceRemovalQuery.addCriteria(overdueWarehouseServiceRemovalStockCriteria);
		List<WarehouseService> warehouseServicesForRemoval = mongoTemplate.find(overdueWarehouseServiceRemovalQuery, WarehouseService.class);
		for (WarehouseService warehouseService : warehouseServicesForRemoval) {
			logger.info("removing warehouse service by id: " + warehouseService.getId());
			WriteResult removalResult = mongoTemplate.remove(warehouseService);
			if (1 != removalResult.getN()) {
				logger.info("failed to remove warehouse service, id: " + warehouseService.getId());
			} else {
				logger.info("warehouse service has been removed, id: " + warehouseService.getId());
			}
		}
		
		// remove overdue vehicles
		Query overdueVehicleServiceRemovalQuery = new Query();
		Criteria overdueVehicleServiceRemovalStockCriteria = Criteria.where("stock").lte(Integer.valueOf(0));
		overdueVehicleServiceRemovalQuery.addCriteria(overdueVehicleServiceRemovalStockCriteria);
		List<VehicleService> vehicleServicesForRemoval = mongoTemplate.find(overdueVehicleServiceRemovalQuery, VehicleService.class);
		for (VehicleService vehicleService : vehicleServicesForRemoval) {
			logger.info("removing vehicle service by id: " + vehicleService.getId());
			WriteResult removalResult = mongoTemplate.remove(vehicleService);
			if (1 != removalResult.getN()) {
				logger.info("failed to remove vehicle service, id: " + vehicleService.getId());
			} else {
				logger.info("vehicle service has been removed, id: " + vehicleService.getId());
			}
		}
	}
	
	public void orderCleanUp() {
		LocalDateTime checkDate = LocalDateTime.now();
		checkDate = checkDate.minusMinutes(20);

		// process overdue vehicle orders for payment
		Query vechileInitialQuery = new Query();
		Criteria vehicleInitialDateCriteria = Criteria.where("creationDate").lt(checkDate);
		Criteria vehicleInitialFieldCriteria = Criteria.where("paymentUrl").exists(false);
		Criteria vehicleInitialStatusCriteria = Criteria.where("status").is(CompleteVehicleOrder.STATUS_UNPAID);
		Criteria vehicleInitialAndCriteria = new Criteria();
		vehicleInitialAndCriteria.andOperator(vehicleInitialDateCriteria, vehicleInitialFieldCriteria, vehicleInitialStatusCriteria);
		vechileInitialQuery.addCriteria(vehicleInitialAndCriteria);
		List<CompleteVehicleOrder> vehicleInitialList = mongoTemplate.find(vechileInitialQuery, CompleteVehicleOrder.class);
		for (CompleteVehicleOrder vehicleOrder : vehicleInitialList) {
			logger.info("process overdue vehicle order for payment, id: " + vehicleOrder.getId());
			Query query = new Query();
			Criteria criteria = Criteria.where("id").is(vehicleOrder.getId());
			query.addCriteria(criteria);
			Update update = new Update();
			update.set("status", CompleteVehicleOrder.STATUS_CANCELED);
			if (!mongoTemplate.updateFirst(query, update, CompleteVehicleOrder.class).isUpdateOfExisting()) {
				logger.error("failed to update status for overdue vehicle order, id: " + vehicleOrder.getId());
			}
		}
		Query vehicleQuery = new Query();
		Criteria vehicleDateCriteria = Criteria.where("lastUpdateTime").lt(checkDate);
		Criteria vehicleFieldCriteria = Criteria.where("paymentUrl").exists(true);
		Criteria vehicleStatusCriteria = Criteria.where("status").is(CompleteVehicleOrder.STATUS_UNPAID);
		Criteria vehicleAndCriteria = new Criteria();
		vehicleAndCriteria.andOperator(vehicleDateCriteria, vehicleFieldCriteria, vehicleStatusCriteria);
		vehicleQuery.addCriteria(vehicleAndCriteria);
		List<CompleteVehicleOrder> vehicleList = mongoTemplate.find(vehicleQuery, CompleteVehicleOrder.class);
		for (CompleteVehicleOrder vehicleOrder : vehicleList) {
			logger.info("process overdue vehicle order for payment, id: " + vehicleOrder.getId());
			Query query = new Query();
			Criteria criteria = Criteria.where("id").is(vehicleOrder.getId());
			query.addCriteria(criteria);
			Update update = new Update();
			update.set("status", CompleteVehicleOrder.STATUS_CANCELED);
			if (!mongoTemplate.updateFirst(query, update, CompleteVehicleOrder.class).isUpdateOfExisting()) {
				logger.error("failed to update status for overdue vehicle order, id: " + vehicleOrder.getId());
			}
		}
		
		// process overdue warehouse orders for payment
		Query warehouseInitialQuery = new Query();
		Criteria warehouseInitialDateCriteria = Criteria.where("creationDate").lt(checkDate);
		Criteria warehouseInitialFieldCriteria = Criteria.where("paymentUrlForAlipay").exists(false);
		Criteria warehouseInitialStatusCriteria = Criteria.where("status").is(WarehouseOrder.STATUS_UNPAID);
		Criteria warehouseInitialAndCriteria = new Criteria();
		warehouseInitialAndCriteria.andOperator(warehouseInitialDateCriteria, warehouseInitialFieldCriteria, warehouseInitialStatusCriteria);
		warehouseInitialQuery.addCriteria(warehouseInitialAndCriteria);
		List<WarehouseOrder> warehouseInitialList = mongoTemplate.find(warehouseInitialQuery, WarehouseOrder.class);
		for (WarehouseOrder warehouseOrder : warehouseInitialList) {
			logger.info("process overdue warehouse order for payment, id: " + warehouseOrder.getId());
			Query query = new Query();
			Criteria criteria = Criteria.where("id").is(warehouseOrder.getId());
			query.addCriteria(criteria);
			Update update = new Update();
			update.set("status", WarehouseOrder.STATUS_CANCELED);
			if (!mongoTemplate.updateFirst(query, update, WarehouseOrder.class).isUpdateOfExisting()) {
				logger.error("failed to update status for overdue warehouse order, id: " + warehouseOrder.getId());
			}
		}
		Query warehouseQuery = new Query();
		Criteria warehouseDateCriteria = Criteria.where("lastUpdateTime").lt(checkDate);
		Criteria warehouseFieldCriteria = Criteria.where("paymentUrlForAlipay").exists(true);
		Criteria warehouseStatusCriteria = Criteria.where("status").is(WarehouseOrder.STATUS_UNPAID);
		Criteria warehouseAndCriteria = new Criteria();
		warehouseAndCriteria.andOperator(warehouseDateCriteria, warehouseFieldCriteria, warehouseStatusCriteria);
		warehouseQuery.addCriteria(warehouseAndCriteria);
		List<WarehouseOrder> warehouseList = mongoTemplate.find(warehouseQuery, WarehouseOrder.class);
		for (WarehouseOrder warehouseOrder : warehouseList) {
			logger.info("process overdue warehouse order for payment, id: " + warehouseOrder.getId());
			Query query = new Query();
			Criteria criteria = Criteria.where("id").is(warehouseOrder.getId());
			query.addCriteria(criteria);
			Update update = new Update();
			update.set("status", WarehouseOrder.STATUS_CANCELED);
			if (!mongoTemplate.updateFirst(query, update, WarehouseOrder.class).isUpdateOfExisting()) {
				logger.error("failed to update status for overdue warehouse order, id: " + warehouseOrder.getId());
			}
		}
		
		// process overdue product orders for payment
		Query productInitialQuery = new Query();
		Criteria productInitialDateCriteria = Criteria.where("creationDate").lt(checkDate);
		Criteria productInitialFieldCriteria = Criteria.where("paymentUrl").exists(false);
		Criteria productInitialStatusCriteria = Criteria.where("status").is(ProductOrder.STATUS_UNPAID);
		Criteria productInitialAndCriteria = new Criteria();
		productInitialAndCriteria.andOperator(productInitialDateCriteria, productInitialFieldCriteria, productInitialStatusCriteria);
		productInitialQuery.addCriteria(productInitialAndCriteria);
		List<ProductOrder> productInitialList = mongoTemplate.find(productInitialQuery, ProductOrder.class);
		for (ProductOrder productOrder : productInitialList) {
			logger.info("process overdue product order for payment, id: " + productOrder.getId());
			Query query = new Query();
			Criteria criteria = Criteria.where("id").is(productOrder.getId());
			query.addCriteria(criteria);
			Update update = new Update();
			update.set("status", ProductOrder.STATUS_CANCELED);
			if (!mongoTemplate.updateFirst(query, update, ProductOrder.class).isUpdateOfExisting()) {
				logger.error("failed to update status for overdue product order, id: " + productOrder.getId());
			}
		}
		Query productQuery = new Query();
		Criteria productDateCriteria = Criteria.where("lastUpdateTime").lt(checkDate);
		Criteria productFieldCriteria = Criteria.where("paymentUrl").exists(true);
		Criteria productStatusCriteria = Criteria.where("status").is(ProductOrder.STATUS_UNPAID);
		Criteria productAndCriteria = new Criteria();
		productAndCriteria.andOperator(productDateCriteria, productFieldCriteria, productStatusCriteria);
		productQuery.addCriteria(productAndCriteria);
		List<ProductOrder> productList = mongoTemplate.find(productQuery, ProductOrder.class);
		for (ProductOrder productOrder : productList) {
			logger.info("process overdue product order for payment, id: " + productOrder.getId());
			Query query = new Query();
			Criteria criteria = Criteria.where("id").is(productOrder.getId());
			query.addCriteria(criteria);
			Update update = new Update();
			update.set("status", ProductOrder.STATUS_CANCELED);
			if (!mongoTemplate.updateFirst(query, update, ProductOrder.class).isUpdateOfExisting()) {
				logger.error("failed to update status for overdue product order, id: " + productOrder.getId());
			}
		}
	}
	
	@Autowired
	private MongoTemplate mongoTemplate;
	
	@Autowired
	private ProductDataManager productDataManager;
	
	private static final Logger logger = LoggerFactory.getLogger(MaintenanceController.class);
}
